package com.heo.protocol;

import java.net.InetSocketAddress;
import java.util.List;

import org.apache.log4j.Logger;

import com.heo.protocol.packet.BasePacket;
import com.heo.protocol.packet.SocketPacket;
import com.heo.pub.ServerConfig;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;

/**
 * <pre>
 *  
 * 消息头格式
 * +——------——+——------——+——-----——+——-----——+
 * |消息长度      |命令ID     |消息流水号|重发次数   |
 * +——------——+——------——+——-----——+——-----——+ 
 * 1.上述4个部分，分别是short，int，short型。分别占2，2，2，1个字节
 * 2.消息长度是整个数据包的长度。
 * 3.要传输的数据,长度不应该超过2048，防止socket流的攻击
 * </pre>
 */

public class DeviceProtocolDecoder extends ByteToMessageDecoder
{
  private static Logger log = Logger.getLogger(DeviceProtocolDecoder.class);

  /**
   * <pre>
   * 消息长度，2个字节
   * 命令ID，2个字节
   * 消息流水，2个字节
   * </pre>
   */
  public final int HEAD_LENGTH = 2 + 2 + 2 + 1;

  @Override
  protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception
  {
    String channelId = ctx.channel().id().asShortText();
    String remoteIpAddress = ((InetSocketAddress) ctx.channel().remoteAddress()).getAddress().getHostAddress();
    String logHeader = "[server][" + channelId + "][" + remoteIpAddress + "]";

    if (in.readableBytes() < HEAD_LENGTH)
    {
      // 这个HEAD_LENGTH是我们用于表示头长度的字节数。
      // 当小于头字节的长度时，表示数据还没传过来。
      return;
    }
    // 防止socket字节流攻击
    // 防止，客户端传来的数据过大
    // 因为，太大的数据，是不合理的
    // 不可以这样限制输入流。因为客户端发太快的时候，这里读到的数据确实很会长，如果限制的话，会导致丢包。因为链路都是认证后才可以发流上来的。所以通过登录验证就可以解决被攻击的问题。因为发的数据不对，会被马上断开。后面根本没办法继续发。
    // 需要在配置文件里关闭
    if (ServerConfig.MAX_READABLE_BYTES_LENGTH != 0 && in.readableBytes() > ServerConfig.MAX_READABLE_BYTES_LENGTH)
    {
      log.error(logHeader + "收到的字节流超过允许的最大长度。");
      in.skipBytes(in.readableBytes());
      return;
    }

    in.markReaderIndex(); // 我们标记一下当前的readIndex的位置
    Short totalLength = in.readShort(); // 消息总长度，客户端传过来。ByteBuf的readUnsignedShort()方法会让他的readIndex增加2。因为每个int是用4个字节表示。1-2字节
    Short commandId = in.readShort();// 读取命令ID，3-4字节
    Short sequenceId = in.readShort();// 读取流水号，5-6字节
    byte sendCount = in.readByte();// 读取发送次数，1个字节
    if (totalLength < HEAD_LENGTH)
    {
      // 我们读到的消息体长度为0，这是不应该出现的情况，如果出现这情况，表示客户端送过来的参数是错的，所以关闭连接。
      log.error(logHeader + "消息头中定义的数据长度小于实际长度。连接被断开。");
      ctx.close();
      return;
    }
    // 上面执行了3次in.readShort()方法后，此时in.readableBytes()再读到的，就是消息的内容。
    if (in.readableBytes() < totalLength - HEAD_LENGTH)
    {
      // 读到的消息体长度如果小于我们传送过来的消息长度，则resetReaderIndex.
      // 这个配合markReaderIndex使用的。把readIndex重置到mark的地方
      // 这里表示拆包的情况，相当于数据还没有传送完。
      in.resetReaderIndex();
      return;
    }
    byte[] body = new byte[totalLength - HEAD_LENGTH]; // 传输正常
    in.readBytes(body);

    SocketPacket socketPacket = new SocketPacket(logHeader, totalLength, commandId, sequenceId, sendCount, body);

    out.add(socketPacket);
  }
}
